<?php
/**
* This is a library kit for webappkit.
* This is a PHP4 version only
* @link http://webappkit.net
* @package xpath
* @author J.Ducastel <nospam0@ducastel.name>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

/**
* checks for dom_xml extension
*/
if (!function_exists("domxml_open_mem")) {
    trigger_error("dom_xml extension is required.",E_USER_WARNING);
    return;
}

/**
* Encapsulates a domNode and provides XPath syntax access methods.
* @uses domNode
* @author J.Ducastel <nospam0@ducastel.name>
* @todo setAttribute(), getAttribute()
*/
class xpathDomNode {
/*--------------------------------------------------------------------
CONSTRUCTOR
--------------------------------------------------------------------*/
    /**
    * constructor alias
    * @access public
    */
    function xpathDomNode(&$src) {
        $this->__construct($src);
    }/**/

    /**
    * "real" constructor
    * @access public
    * @param mixed $src domNode or xml string
    */
    function __construct(& $src) {
        // if source is a string, try to build from xml
        if (is_string($src)) {
            $dom=$this->_documentFromXml($src);
        } else if (is_a($src,'domNode')) {
            $dom=&$src;
        }
        // importing domNode if valid
        if (is_a($dom,'domnode')) {
            $this->node=&$dom;
            // setting owner document
            $this->doc=$this->node->owner_document();
            // setting xpath context
            $this->context=xpath_new_context($this->node);
            // setting namespaces
            $this->_setNamespaces();
        }
    }/**/
/*--------------------------------------------------------------------
PUBLIC STATIC METHODS
--------------------------------------------------------------------*/

/*--------------------------------------------------------------------
PUBLIC INSTANCE METHODS
--------------------------------------------------------------------*/
    /**
    * return mapped node's content as a string.
    * @access public
    * @return string
    */
    function getContent() {
        return $this->node->get_content();
    }/**/

    /**
    * sets mapped node's content.
    * @param string $content
    * @access public
    * @return bool
    * @todo domNode insertion as child node
    */
    function setContent(& $content) {
        return $this->node->set_content($content);
    }/**/

    /**
    * returns node's tagname
    * @access public
    * @return string
    */
    function getTagName() {
        return $this->node->tagname();
    }/**/

    /**
    * return xml dump of the node
    * @param
    * @access public
    * @return string
    * /
    function getXml() {
        if (is_a($this->node,'domdocument'))
            return $this->node->dump_mem();
        return $this->node->dump_node($this->doc);
    }/**/

    /**
    * gets mapped node instance reference
    * @access public
    * @return domNode
    */
    function & getNode() {
        return $this->node;
    }/**/


    /**
    * return node's owner document reference.
    * @access public
    * @return domDocument
    */
    function & getDocument() {
        return $this->node->owner_document();
    }/**/

    /**
     * return's document as an XML string
     * @access public
     * @param bool $indenting default true
     * @param string $encoding default is 'UTF-8'
     * @return string
     */
    function getDocumentXml($indent=true,$encoding='UTF-8') {
        $doc=$this->node->owner_document();
        return $doc->dump_mem($indent,$encoding);
    }/**/

    /**
    * build, append and return node's child.
    * @param string $tagName tag name oif the node to create
    * @access public
    * @return ooXmlRpcDomNode created node
    */
    function & makeChild($tagName) {
        // build dom node
        $doc=&$this->getDocument();
        $dom=$doc->create_element($tagName);
        //append domnode
        $this->node->append_child($dom);
        $node=&new ooXmlRpcDomNode($dom);
        return $node;
    }/**/

    /**
    * return targeted nodes.
    * @param string $xpath XPath targeting syntax
    * @access public
    * @return array domNode instances
    */
    function searchNodes($xpath) {
        return $this->_collectXpath($xpath);
    }/**/

    /**
    * return targeted nodes content.
    * @param string $xpath XPath targeting syntax
    * @access public
    * @return array strings
    */
    function searchContent($xpath) {
        $nodes=$this->_collectXpath($xpath);
        $results=array();
        for ($n=0; $n<count($nodes); $n++) {
            $results[]=$nodes[$n]->get_content();
        }
        return $results;
    }/**/

    /**
    * search nodes and replace them by passed value.
    * @access public
    * @param string $xpath XPath targeting syntax
    * @param mixed $value the value which will replace the targeted nodes
    * @return int affected nodes count
    */
    function searchAndSetNodes($xpath,$value) {
        // getting targeted nodes
        $nodes=&$this->_collectXpath($xpath);
        // looping results
        $count=0;
        for ($n=0; $n<count($nodes); $n++) {
            //$results[]=$nodes[$n]->get_content();
            if ($this->_replaceNode($nodes[$n],$value))
                // success
                $count++;
        }
        return $count;
    }/**/

    /**
    * search nodes and set their content.
    * @access public
    * @param string $xpath XPath targeting syntax
    * @param mixed $value the value which will be applied to targeted nodes
    * @return int setted nodes
    */
    function searchAndSetContent($xpath,$value) {
        // getting targeted nodes
        $nodes=&$this->_collectXpath($xpath);
        // looping results
        $count=0;
        for ($n=0; $n<count($nodes); $n++) {
            //$results[]=$nodes[$n]->get_content();
            if ($this->_replaceNodeContent($nodes[$n],$value))
                // success
                $count++;
        }
        return $count;
    }/**/

    /**
     * removes selected nodes
     * @access public
     * @param string $xpath XPath targeting syntax
     * @return int removed nodes count
     */
    function searchAndRemoveNodes($xpath) {
        // getting targeted nodes
        $nodes=&$this->_collectXpath($xpath);
        // looping results
        $count=0;
        for ($n=0; $n<count($nodes); $n++) {
            if ($this->_removeNode($nodes[$n]))
                // success
                $count++;
        }
        return $count;
    }

    /**
    * get XPathContext instance
    * @access public
    * @return XPathContext
    */
    function & getContext() {
        /*if (!$this->context and is_a($this->node,'domnode')) {
            $this->context=xpath_new_context($this->node);
        }*/
        return $this->context;
    }/**/

    /**
    * Registers a namespace's prefix
    * @param string $prefix
    * @param string $uri
    * @access public
    * @return bool
    */
    function registerNamespace($prefix,$uri) {
        //$context=&$this->getContext();
        if (xpath_register_ns($this->context,$prefix,$uri)) {
            $this->namespaces[$prefix]=$uri;
            return true;
        }
        return false;
    }/**/

    /**
    * is a valid xml document file ?
    * @access public
    * @return bool
    */
    function isValid() {
        return is_a($this->node,'domnode');
    }/**/
/*--------------------------------------------------------------------
VARS / MEMBERS
--------------------------------------------------------------------*/
    /**
    * @var domNode encapsulated dom node
    */
    var $node;

    /**
     * @var domDocument owner document
     */
    var $doc;

    /**
     * @var XPathContext
     */
    var $context;

    /**
     * @var array registered namespaces prefix => uri
     */
    var $namespaces=array();

/*--------------------------------------------------------------------
PRIVATE / PROTECTED METHODS
--------------------------------------------------------------------*/
    /**
    * collects xpath-targeted elements
    * @param string $xpath
    * @access protected
    * @return array
    */
    function & _collectXpath($xpath) {
        $results=array();
        // getting xpath context
        if ($context=&$this->getContext()) {
            // silent warnings...
            $xO=@xpath_eval($context,$xpath);
            if (is_a($xO,'xpathObject')) {
                switch ($xO->type) {
                    case XPATH_NODESET:
                        $results=$xO->nodeset;
                        break;
                    case XPATH_STRING:
                        $results=array($xO->value);
                        break;
                    case XPATH_BOOLEAN:
                    case XPATH_NUMBER:
                    case XPATH_POINT:
                    case XPATH_RANGE:
                    case XPATH_LOCATIONSET:
                    case XPATH_UNDEFINED:
                    default:
                        trigger_error("unkown node type",E_USER_WARNING);
                        echo '<pre>'; print_r($xO);
                }
            } else {
                echo $this->getDocumentXml();
                exit;
            }
        }
        // echo $xpath;
        return $results;
    }/**/

    /**
    * will be called by __construct()
    * @access protected
    * @return bool
    */
    function _setNamespaces() {
        return true;
    }/**/

    /**
    * tries to build a domDocument from an xml string
    * @access protected
    * @param string $xml xml string
    * @return xpathDomNode or false if failed
    */
    function & _documentFromXml($xml) {
        // checking string start
        if (substr($xml,0,1)!='<') {
            $f=false; return $f;
        }
        // chargement en memoire
        $error=array();
        //$dom=@domxml_open_mem($xml);
        $dom=domxml_open_mem($xml,DOMXML_LOAD_PARSING,$error);
        if (!$dom) {
            // failed to build
            if (is_array($error)) {
                foreach ($error as $errorline) {    # Loop through all errors
                    $txt=$errorline['errormessage']
                    //." Node  : " . $errorline['nodename'] . "\n"
                    ." Line  : " . $errorline['line'] . "\n"
                    ." Column : " . $errorline['col'] . "\n\n";
                    trigger_error($txt,E_USER_NOTICE);
                }
            }
            $f=false; return $f;
        }
        return $dom;
    }/**/

    /**
     * converts passed value to a domNode for use
     * also attaches it to the
     **/
    function & _toNode(& $value) {
        switch (true) {
            case is_a($value,'domnode'):
                return $value;
            case is_string($value):
            case is_numeric($value):
                $node=&$this->doc->create_text_node(strval($value));
                // echo "<pre>$value =>"; print_r($node); echo '</pre>';
                return $node;
            case is_null($value):
                $node=&$this->doc->create_comment("NULL value inserted");
                return $node;
            default:
                // ???
                $type=gettype($value);
                $node=&$this->doc->create_comment("unsupported type ($type) for node conversion");
                return $node;
        }
    }

    /**
    * replaces a node by something else
    * @param domNode $node
    * @param mixed $new_content
    * @access protected
    * @return bool
    */
    function _replaceNode(&$node, $new_content ) {
        $new_node=$this->_toNode($new_content);
        // print_r($new_node);
        $old=$node->replace_node($new_node);
        return is_a($old,'domnode');
    }/**/

    /**
     * removes a node
     * @param domNode $node
     * @return bool
     */
    function _removeNode(&$node) {
        $parent=&$node->parent_node();
        if ($parent->remove_child($node))
            return true;
        else
            return false;
    }

    /**
    * replaces a node's content
    * @param domNode $node
    * @param mixed $new_content
    * @access protected
    * @return bool
    */
    function _replaceNodeContent(&$node, $new_content ) {
        //$dom = &$node->owner_document();
        $kids = &$node->child_nodes();
        foreach ( $kids as $kid )
            if ( $kid->node_type() == XML_TEXT_NODE )
                $node->remove_child ($kid);
        return $node->set_content($new_content);
    }/**/

    /**
    *
    * @param
    * @access protected
    * @return
    * /
    function () {

    }/**/
}
?>